#include <iostream>
#include <math.h>
#include <limits.h>
#include "ALU_DEF.h"
#include "ALU_ADD.h"

using namespace std;

ALU_ADD::ALU_ADD()
{
	OpCode     = NOP_OPCODE;
	inputA     = 0;
	inputB     = 0;
	regDst     = 0;
	inputFA    = 0.0f;
	inputFB    = 0.0f;
	fu_lrf     = NULL;
	
	CyclesLeft = 0;
	Ready      = true;
	outputZ    = 0;
	outputFZ   = 0.0f;
}

void ALU_ADD::update()
{
	ALU_ADD_ENTRY temp;
	ALU_ADD_ENTRY temp2;
	int in_flight = (int) add_pipeline.size();
	temp.Ready = true;
	temp.OpCode = NOP_OPCODE;

	update_queue();			//Get the current instruction going

	if (Ready == false)		//If the instruction did not complete store the values
	{
		temp.CyclesLeft = CyclesLeft;
		temp.inputA = inputA;
		temp.inputB = inputB;
		temp.inputFA = inputFA;
		temp.inputFB = inputFB;
		temp.OpCode = OpCode;
		temp.outputFZ = outputFZ;
		temp.outputZ = outputZ;
		temp.Ready = Ready;
		temp.regDst = regDst;
	}

	for (int i = 0; i < in_flight; i++)		//for all in flight instructions update their progress
	{
		temp2 = add_pipeline.front();
		add_pipeline.pop();
		inputA = temp2.inputA;
		inputB = temp2.inputB;
		inputFA = temp2.inputFA;
		inputFB = temp2.inputFB;
		OpCode = temp2.OpCode;
		outputFZ = temp2.outputFZ;
		outputZ = temp2.outputZ;
		Ready = temp2.Ready;
		regDst = temp2.regDst;
		CyclesLeft = temp2.CyclesLeft;

		update_queue();

		if (Ready == false)				//If the instruction still has not finished put it back in the queue
		{
			temp2.CyclesLeft = CyclesLeft;
			temp2.inputA = inputA;
			temp2.inputB = inputB;
			temp2.inputFA = inputFA;
			temp2.inputFB = inputFB;
			temp2.OpCode = OpCode;
			temp2.outputFZ = outputFZ;
			temp2.outputZ = outputZ;
			temp2.Ready = Ready;
			temp2.regDst = regDst;
			add_pipeline.push(temp2);
		}
	}

	if (temp.Ready == false && temp.OpCode != NOP_OPCODE)	//Push the new instruction onto the queue if it needs it
		add_pipeline.push(temp);

	OpCode = NOP_OPCODE;
	Ready = true;
}

void ALU_ADD::update_queue()
{
	long long int          temp  = 0;
	unsigned long long int temp2 = 0;
	unsigned int           temp3 = 0;

	if(Ready)
	{
		if( NOP_OPCODE != OpCode )
		{
			switch (OpCode)
			{
			case IADD_OPCODE:
				CyclesLeft = IADD_DELAY;
				break;
			case ISUB_OPCODE:
				CyclesLeft = ISUB_DELAY;
				break;
			case IABD_OPCODE:
				CyclesLeft = IABD_DELAY;
				break;
			case UABD_OPCODE:
				CyclesLeft = UABD_DELAY;
				break;
			case ILT_OPCODE:
				CyclesLeft = ILT_DELAY;
				break;
			case ULT_OPCODE:
				CyclesLeft = ULT_DELAY;
				break;
			case ILE_OPCODE:
				CyclesLeft = ILE_DELAY;
				break;
			case ULE_OPCODE:
				CyclesLeft = ULE_DELAY;
				break;
			case IEQ_OPCODE:
				CyclesLeft = IEQ_DELAY;
				break;
			case NEQ_OPCODE:
				CyclesLeft = NEQ_DELAY;
				break;
			case AND_OPCODE:
				CyclesLeft = AND_DELAY;
				break;
			case OR_OPCODE:
				CyclesLeft = OR_DELAY;
				break;
			case XOR_OPCODE:
				CyclesLeft = XOR_DELAY;
				break;
			case NOT_OPCODE:
				CyclesLeft = NOT_DELAY;
				break;
			case IADDS_OPCODE:
				CyclesLeft = IADDS_DELAY;
				break;
			case UADDS_OPCODE:
				CyclesLeft = UADDS_DELAY;
				break;
			case ISUBS_OPCODE:
				CyclesLeft = ISUBS_DELAY;
				break;
			case USUBS_OPCODE:
				CyclesLeft = USUBS_DELAY;
				break;
			case SHIFT_OPCODE:
				CyclesLeft = SHIFT_DELAY;
				break;
			case SHIFTA_OPCODE:
				CyclesLeft = SHIFT_DELAY;
				break;
			case ROTATE_OPCODE:
				CyclesLeft = ROTATE_DELAY;
				break;
			case FADD_OPCODE:
				CyclesLeft = FADD_DELAY;
				break;
			case FSUB_OPCODE:
				CyclesLeft = FSUB_DELAY;
				break;
			case FABS_OPCODE:
				CyclesLeft = FABS_DELAY;
				break;
			case FLT_OPCODE:
				CyclesLeft = FLT_DELAY;
				break;
			case FLE_OPCODE:
				CyclesLeft = FLE_DELAY;
				break;
			case FTOI_OPCODE:
				CyclesLeft = FTOI_DELAY;
				break;
			case FFRAC_OPCODE:
				CyclesLeft = FFRAC_DELAY;
				break;
			case ITOF_OPCODE:
				CyclesLeft = ITOF_DELAY;
				break;
			}

			Ready = false;
			update_queue();
		}
	}
	else //Not ready, processing operation
	{
		CyclesLeft--;

		if( 0 == CyclesLeft )
		{
			switch( OpCode )
			{
				case IADD_OPCODE:
					outputZ = inputA + inputB;
					fu_lrf->write(regDst, (unsigned char) outputZ);
					break;
				case ISUB_OPCODE:
					outputZ = inputA - inputB;
					fu_lrf->write(regDst, (unsigned char) outputZ);
					break;
				case IABD_OPCODE:
					outputZ = abs(inputA - inputB);
					fu_lrf->write(regDst, (unsigned char) outputZ);
					break;
				case UABD_OPCODE:
					outputZ = abs((int) ((unsigned int) inputA - (unsigned int) inputB));
					fu_lrf->write(regDst, (unsigned char) outputZ);
					break;
				case ILT_OPCODE:
					outputZ = (inputA < inputB);
					fu_lrf->write(regDst, (unsigned char) outputZ);
					break;
				case ULT_OPCODE:
					outputZ = ((unsigned int) inputA < (unsigned int) inputB);
					fu_lrf->write(regDst, (unsigned char) outputZ);
					break;
				case ILE_OPCODE:
					outputZ = (inputA <= inputB);
					fu_lrf->write(regDst, (unsigned char) outputZ);
					break;
				case ULE_OPCODE:
					outputZ = ((unsigned int) inputA <= (unsigned int) inputB);
					fu_lrf->write(regDst, (unsigned char) outputZ);
					break;
				case IEQ_OPCODE:
					outputZ = (inputA == inputB);
					fu_lrf->write(regDst, (unsigned char) outputZ);
					break;
				case NEQ_OPCODE:
					outputZ = (inputA != inputB);
					fu_lrf->write(regDst, (unsigned char) outputZ);
					break;
				case AND_OPCODE:
					outputZ = inputA & inputB;
					fu_lrf->write(regDst, (unsigned char) outputZ);
					break;
				case OR_OPCODE:
					outputZ = inputA | inputB;
					fu_lrf->write(regDst, (unsigned char) outputZ);
					break;
				case XOR_OPCODE:
					outputZ = inputA ^ inputB;
					fu_lrf->write(regDst, (unsigned char) outputZ);
					break;
				case NOT_OPCODE:
					outputZ = !inputA;
					fu_lrf->write(regDst, (unsigned char) outputZ);
					break;
				case IADDS_OPCODE:
					temp = (long long int) inputA + (long long int) inputB;
					if (temp > UCHAR_MAX)
						outputZ = UCHAR_MAX;
					else if (temp < UCHAR_MIN)
						outputZ = UCHAR_MIN;
					else
						outputZ = inputA + inputB;
					fu_lrf->write(regDst, (unsigned char) outputZ);
					break;
				case UADDS_OPCODE:
					temp2 = (unsigned long long int) inputA + (unsigned long long int) inputB;
					if (temp2 > UINT_MAX)
						outputZ = UINT_MAX;
					else
						outputZ = (unsigned int) inputA + (unsigned int) inputB;
					fu_lrf->write(regDst, (unsigned char) outputZ);
					break;
				case ISUBS_OPCODE:
					temp = (long long int) inputA - (long long int) inputB;
					if (temp > INT_MAX)
						outputZ = INT_MAX;
					else if (temp < INT_MIN)
						outputZ = INT_MIN;
					else
						outputZ = inputA - inputB;
					fu_lrf->write(regDst, (unsigned char) outputZ);
					break;
				case USUBS_OPCODE:
					temp = (unsigned long long int) inputA - (unsigned long long int) inputB;
					if (temp < 0)
						outputZ = 0;
					else
						outputZ = inputA - inputB;
					fu_lrf->write(regDst, (unsigned char) outputZ);
					break;
				case SHIFT_OPCODE:
					outputZ = (unsigned) inputA >> inputB;		    // May not work in all compilers
					fu_lrf->write(regDst, (unsigned char) outputZ);
					break;
				case SHIFTA_OPCODE:
					outputZ = inputA >> inputB;					    // Works for VISUAL STUDIO
					fu_lrf->write(regDst, (unsigned char) outputZ);
					break;
				case ROTATE_OPCODE:
					outputZ = (((unsigned int) inputA >> (unsigned int) inputB) | 
						        (unsigned int) inputA << (32-(unsigned int) inputB));
					fu_lrf->write(regDst, (unsigned char) outputZ);
					break;
				case FADD_OPCODE:
					outputFZ = inputFA + inputFB;
					fu_lrf->write(regDst, (unsigned char) outputZ);
					break;
				case FSUB_OPCODE:
					outputFZ = inputFA - inputFB;
					fu_lrf->write(regDst, (unsigned char) outputZ);
					break;
				case FABS_OPCODE:
					outputFZ = abs(inputFA);
					fu_lrf->write(regDst, (unsigned char) outputZ);
					break;
				case FLT_OPCODE:
					outputZ = inputFA < inputFB;
					fu_lrf->write(regDst, (unsigned char) outputZ);
					break;
				case FLE_OPCODE:
					outputZ = inputFA <= inputFB;
					fu_lrf->write(regDst, (unsigned char) outputZ);
					break;
				case FTOI_OPCODE:
					outputZ = (int) inputFA;
					fu_lrf->write(regDst, (unsigned char) outputZ);
					break;
				case FFRAC_OPCODE:
					outputFZ = inputFA - floor(inputFA);
					fu_lrf->write(regDst, (unsigned char) outputZ);
					break;
				case ITOF_OPCODE:
					outputFZ = (float) inputA;
					fu_lrf->write(regDst, (unsigned char) outputZ);
					break;
			}

			OpCode = NOP_OPCODE;
			Ready = true;
		}
	}
}

int ALU_ADD::getOutput()
{
	return outputZ;
}

float ALU_ADD::getOutputF()
{
	return outputFZ;
}

bool ALU_ADD::isReady()
{
	return Ready;
}

bool ALU_ADD::isFinished()
{
	return add_pipeline.empty();
}

